﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DataTypesManager.cs" company="CodePlex">
//   The MIT License
//   Copyright (c) 2013 Julien Blin
//   
//   Permission is hereby granted, free of charge, to any person obtaining a copy
//   of this software and associated documentation files (the "Software"), to deal
//   in the Software without restriction, including without limitation the rights
//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//   copies of the Software, and to permit persons to whom the Software is
//   furnished to do so, subject to the following conditions:
//   
//   The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//   THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace UCodeFirst.DataTypes
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    using Umbraco.Core;
    using Umbraco.Core.Logging;
    using Umbraco.Core.Models;

    /// <summary>
    /// Manager responsible for synchronizing data types.
    /// </summary>
    public class DataTypesManager : UCodeFirstManagerBase
    {
        /// <summary>
        /// The key to store and retrieve the list of synchronized data type names.
        /// </summary>
        private const string DataTypesListKey = "DataTypesList";

        /// <summary>
        /// The synchronized data type names.
        /// </summary>
        private IList<string> dataTypeNames;

        /// <summary>
        /// Returns <see cref="ManagerPriorities.DataTypes"/>.
        /// </summary>
        public override int Priority
        {
            get
            {
                return ManagerPriorities.DataTypes;
            }
        }

        /// <summary>
        /// All the managers <see cref="UCodeFirstManagerBase.Synchronize"/> methods will run after all the managers <see cref="UCodeFirstManagerBase.BeforeSynchronization"/> method have run,
        /// <b>only</b> if the synchronization happens.<br />
        /// They run in the order specified by the <see cref="UCodeFirstManagerBase.Priority"/>.
        /// </summary>
        /// <param name="httpApplication">
        ///     The <see cref="UmbracoApplicationBase"/>.
        /// </param>
        /// <param name="applicationContext">
        ///     The <see cref="ApplicationContext"/>.
        /// </param>
        public override void Synchronize(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext)
        {
            this.dataTypeNames = new List<string>();

            var dataTypeBaseTypes = TypesScanner.GetClassesInBinThatAreAssignableFrom<DataTypeBase>().ToList();
            var dataTypeBaseMissingDataTypeAttribute = dataTypeBaseTypes.Where(x => !x.HasAttr<DataTypeAttribute>()).ToList();
            if (dataTypeBaseMissingDataTypeAttribute.Count > 0)
            {
                throw new UCodeFirstException(Resources.MissingDataTypeAttribute.InvariantFormat(string.Join(", ", dataTypeBaseMissingDataTypeAttribute)));
            }

            LogHelper.Debug<DataTypesManager>(Resources.FoundNDataTypeBase.InvariantFormat(dataTypeBaseTypes.Count));
            var dataTypeService = ApplicationContext.Current.Services.DataTypeService;
            var dataTypesDefinitions = dataTypeService.GetAllDataTypeDefinitions().ToList();

            foreach (var dataTypeBaseType in dataTypeBaseTypes)
            {
                try
                {
                    LogHelper.Debug<DataTypesManager>(Resources.Synchronizing.InvariantFormat(dataTypeBaseType.Name));

                    var dataTypeAttribute = dataTypeBaseType.GetAttr<DataTypeAttribute>();
                    var dataTypeName = dataTypeAttribute.GetName(dataTypeBaseType.Name);
                    var renderControlGuid = dataTypeAttribute.GetRenderControlGuid();

                    var umbracoDatatype = dataTypesDefinitions.FirstOrDefault(x => x.Name.InvariantEquals(dataTypeName))
                                          ?? new DataTypeDefinition(-1, renderControlGuid);

                    var dataTypeUpdater = new ConditionalPropertyUpdater<DataTypeDefinition>((DataTypeDefinition)umbracoDatatype);
                    dataTypeUpdater.Update(x => x.Name, dataTypeName);
                    dataTypeUpdater.Update(x => x.ControlId, renderControlGuid);
                    dataTypeUpdater.Update(x => x.DatabaseType, dataTypeAttribute.DatabaseType);
                    
                    if (dataTypeUpdater.HasBeenChanged)
                    {
                        dataTypeService.Save(umbracoDatatype, UCodeFirstConfig.GetUser().Id);
                    }

                    // Manage PreValues.
                    var settingsStorage = new umbraco.cms.businesslogic.datatype.DataEditorSettingsStorage();
                    var existingSettings = settingsStorage.GetSettings(umbracoDatatype.Id);
                    var dataTypeInstance = dataTypeBaseType.CreateInstance<DataTypeBase>();
                    var newSettings = dataTypeInstance.UpdateSettings(existingSettings);
                    if (newSettings != null)
                    {
                        newSettings = newSettings.Select(x => new umbraco.cms.businesslogic.datatype.Setting<string, string>
                        {
                            Key = x.Key ?? string.Empty,
                            Value = x.Value
                        }).ToList();

                        if (!newSettings.SequenceEqual(existingSettings))
                        {
                            settingsStorage.UpdateSettings(umbracoDatatype.Id, newSettings);
                        }
                    }

                    this.dataTypeNames.Add(dataTypeName);
                    LogHelper.Debug<DataTypesManager>(Resources.Synchronized.InvariantFormat(dataTypeBaseType.Name));
                }
                catch (Exception ex)
                {
                    throw new UCodeFirstException(Resources.ErrorWhileSynchronizing.InvariantFormat(dataTypeBaseType), ex);
                }
            }

            LogHelper.Info<DataTypesManager>(Resources.SynchronizedNDataTypes.InvariantFormat(dataTypeBaseTypes.Count));
        }

        /// <summary>
        /// All the managers <see cref="UCodeFirstManagerBase.AfterSynchronization"/> methods will run after all the managers <see cref="UCodeFirstManagerBase.Synchronize"/> method have run,
        /// no matter whether synchronization happens or not.<br />
        /// They run in the order specified by the <see cref="UCodeFirstManagerBase.Priority"/>.
        /// </summary>
        /// <param name="httpApplication">
        ///     The <see cref="UmbracoApplicationBase"/>.
        /// </param>
        /// <param name="applicationContext">
        ///     The <see cref="ApplicationContext"/>.
        /// </param>
        /// <param name="synchronizationHappened">
        /// True if the synchronization happened.
        /// </param>
        public override void AfterSynchronization(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext, bool synchronizationHappened)
        {
            if (synchronizationHappened)
            {
                Debug.Assert(this.dataTypeNames != null, "this.dataTypeNames != null");
                var previouslySynchronizedDataTypes = new List<string>();
                var previouslySynchronizedDataTypesValue = UCodeFirstData.GetValue<DataTypesManager>(DataTypesListKey);
                if (!string.IsNullOrEmpty(previouslySynchronizedDataTypesValue))
                {
                    previouslySynchronizedDataTypes = previouslySynchronizedDataTypesValue.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries).ToList();
                }

                var dataTypesDefinitionsToDelete = umbraco.cms.businesslogic.datatype.DataTypeDefinition.GetAll()
                                                                  .Where(x => previouslySynchronizedDataTypes.InvariantContains(x.Text)
                                                                           && !this.dataTypeNames.InvariantContains(x.Text))
                                                                  .ToList();
                foreach (var dataTypeDefinitionToDelete in dataTypesDefinitionsToDelete)
                {
                    try
                    {
                        LogHelper.Info<DataTypesManager>(Resources.Deleting.InvariantFormat(dataTypeDefinitionToDelete.Text));
                        dataTypeDefinitionToDelete.delete();
                    }
                    catch (Exception ex)
                    {
                        throw new UCodeFirstException(Resources.ErrorWhileDeleting.InvariantFormat(dataTypeDefinitionToDelete.Text), ex);
                    }
                }

                UCodeFirstData.SetValue<DataTypesManager>(DataTypesListKey, string.Join("|", this.dataTypeNames));
            }
        }
    }
}
